#include <assert.h>
#include <gfx/idevice.h>
#include <gfx/iindexbuffer.h>
#include <gfx/ivertexbuffer.h>
#include <gfx/ivertexdeclaration.h>
#include <gfx/renderenvironment.h>
#include <gfx/gl20/mesh/patchmesh.h>
#include <utils/boundingboxbuilder.h>
#include <stdio.h>
#include <string.h>
namespace
{

bool is_power_of_two (unsigned v)
{
  if (v == 0x00)
    {
      return false;
    }

  if (v & 0x01)
    {
      return v == 0x01;
    }
  return is_power_of_two (v >> 1);
}

}

cePatchMeshGL20::cePatchMeshGL20()
  : _dirty(false)
  , _vertices(0)
  , _vertexDeclaration (0)
{
  CE_OBJECT_CONSTR
}

cePatchMeshGL20::~cePatchMeshGL20()
{
  CE_UNSET(_vertexDeclaration);
  CE_UNSET(_vertices);

  for (unsigned i=0, ni=_patches.size(); i<ni; ++i)
    {
      Patch* patch = _patches[i];
      CE_UNSET(patch->indexBuffer);
      delete patch;
    }
  _patches.clear();

}

void cePatchMeshGL20::Draw(const ceRenderEnvironment &renderEnvironment, iDevice *device)
{

  device->SetStreamSource(0, _vertices);
  device->SetVertexDeclaration(_vertexDeclaration);
  for (unsigned i=0, j=_patches.size(); i<j; i++)
    {
      Patch* patch = _patches[i];
      device->SetIndices(patch->indexBuffer);
      device->DrawIndexPrimitives(PT_Triangles, DT_UnsignedInt, patch->assignedIndices);
    }
}

void cePatchMeshGL20::Update(const ceRenderEnvironment &renderEnvironment, iDevice *device)
{
  if (!_dirty && (renderEnvironment.Frame % 100) != 0)
    {
      return;
    }
  for (unsigned i=0, j=_patches.size(); i<j; i++)
    {
      Patch* patch = _patches[i];
      ceVector3f diff = renderEnvironment.CameraLocation - patch->bbox.GetCenter();
      float fact = diff.Length() / patch->maxDistance;

			unsigned stepP = (unsigned)(fact * (float)patch->maxStepP);
      unsigned stepQ = (unsigned)(fact * (float)patch->maxStepQ);
      stepP = 1 << (stepP > patch->maxStepP ? patch->maxStepP : stepP);
      stepQ = 1 << (stepQ > patch->maxStepQ ? patch->maxStepQ : stepQ);

      if (stepP != patch->stepP)
        {
          patch->dirty = true;
          patch->stepP = stepP;
          if (patch->neighbours[PN_NegQ]) patch->neighbours[PN_NegQ]->dirty = true;
          if (patch->neighbours[PN_PosQ]) patch->neighbours[PN_PosQ]->dirty = true;
        }
      if (stepQ != patch->stepQ)
        {
          patch->dirty = true;
          patch->stepQ = stepQ;
          if (patch->neighbours[PN_NegP]) patch->neighbours[PN_NegP]->dirty = true;
          if (patch->neighbours[PN_PosP]) patch->neighbours[PN_PosP]->dirty = true;
        }
    }

  for (unsigned i=0, j=_patches.size(); i<j; i++)
    {
      Patch* patch = _patches[i];
      if (patch->dirty)
        {
          UpdatePatch (patch);
        }
      patch->dirty = false;
    }
  _dirty = false;
}

const ceBoundingBox& cePatchMeshGL20::GetBoundingBox () const
{
	return _bbox;
}

ceBoundingBox& cePatchMeshGL20::GetBoundingBox ()
{
	return _bbox;
}

namespace
{
unsigned get_max_step (unsigned x)
{
  if (x == 1)
    {
      return 0;
    }
  return 1 + get_max_step (x >> 1);
}
}

cePatchMeshGL20::Patch* cePatchMeshGL20::CreatePatch(iDevice* device, unsigned offset, unsigned dp, unsigned dq)
{
  Patch* patch = new Patch();
  memset(patch, 0, sizeof(Patch));
  patch->offset = offset;
  patch->dp = dp;
  patch->dq = dq;
  patch->stepP = 1;
  patch->stepQ = 1;
  patch->dirty = true;
  patch->maxStepP = get_max_step (dp);
  patch->maxStepQ = get_max_step (dq);
  patch->indexBuffer = device->CreateIndexBuffer((dp-1)*(dq-1) * 6 * sizeof(unsigned), 0, BDM_Stream);
  return patch;
}

unsigned cePatchMeshGL20::GetNumberOfPatches() const
{
  return _patches.size ();
}

cePatchMeshGL20::Patch* cePatchMeshGL20::GetPatch(unsigned p)
{
  if (p >= _patches.size ())
    {
      return 0;
    }
  return _patches[p];
}

cePatchMeshGL20::Patch* cePatchMeshGL20::GetPatch(int p, int q)
{
  if (p < 0 || p >= _numPatchP ||
      q < 0 || q >= _numPatchQ)
    {
      return 0;
    }

  return _patches[q * _numPatchP + p];
}

cePatchMeshGL20::Patch* cePatchMeshGL20::GetPatch(int p, int q, bool wrapP, bool wrapQ)
{
  if (p < 0 && wrapP) p = _numPatchP - 1;
  if (p >= _numPatchP && wrapP) p = 0;
  if (q < 0 && wrapQ) q = _numPatchQ - 1;
  if (q >= _numPatchQ && wrapQ) q = 0;

  return GetPatch(p, q);
}

bool cePatchMeshGL20::Initialize (iDevice						*device,
															iVertexBuffer				*vertices, 
															unsigned						numVertexP, 
															unsigned						numVertexQ,
															iVertexDeclaration *vertexDeclaration,
															unsigned						numPatchesP, 
															unsigned						numPatchesQ, 
															float								distance,
															bool								wrapP, 
															bool								wrapQ)
{
	assert (device);
	assert (vertices);
	assert (vertexDeclaration);

	// first initial all private members
  CE_SET(_vertices, vertices);
	_numP = numVertexP;
	_numQ = numVertexQ;
  CE_SET(_vertexDeclaration, vertexDeclaration);

	if (!CreatePatches (device, numPatchesP, numPatchesQ, wrapP, wrapQ)) return false;
	InitializePatches (1);
	if (!UpdatePatchBoundingBoxes (distance)) return false;

	return true;
}



bool cePatchMeshGL20::CreatePatches(iDevice *device, unsigned numP, unsigned numQ, bool wrapP, bool wrapQ)
{
  unsigned testP = _numP - 1;
  unsigned testQ = _numQ - 1;
  // check whether the number of vertices is at least dividable by the number
  // of patches we want
  if (testP % numP) return false;
  if (testQ % numQ) return false;

  unsigned numPerPatchP = testP / numP;
  unsigned numPerPatchQ = testQ / numQ;

  // check if the number of vertices per patch is of power of two
  if (!is_power_of_two(numPerPatchP)) return false;
  if (!is_power_of_two(numPerPatchQ)) return false;

  _wrapP = wrapP;
  _wrapQ = wrapQ;
  _numPatchP = numP;
  _numPatchQ = numQ;
  _patches.clear ();
  for (unsigned q=0; q<numQ; q++)
    {
      unsigned offsetQ = q * numPerPatchQ * _numP;
      for (unsigned p=0; p<numP; p++)
        {
          unsigned offset = offsetQ + (p * numPerPatchP);
          Patch* patch = CreatePatch(device, offset, numPerPatchP+1, numPerPatchQ+1);
          _patches.push_back (patch);
        }
    }

  for (int q=0; q<numQ; q++)
    {
      for (int p=0; p<numP; p++)
        {
          Patch* patch = GetPatch(p, q);
          patch->neighbours[PN_NegP] = GetPatch(p-1, q  , wrapP, wrapQ);
          patch->neighbours[PN_NegQ] = GetPatch(p  , q-1, wrapP, wrapQ);
          patch->neighbours[PN_PosP] = GetPatch(p+1, q  , wrapP, wrapQ);
          patch->neighbours[PN_PosQ] = GetPatch(p  , q+1, wrapP, wrapQ);
        }
    }
  _dirty = true;
  return true;
}
void cePatchMeshGL20::InitializePatches(unsigned stepSize)
{
  if (stepSize == 0) stepSize = 1;
  for (unsigned i=0, j=_patches.size(); i<j; i++)
    {
      Patch* patch = _patches[i];
      patch->stepP = stepSize;
      patch->stepQ = stepSize;
      UpdatePatch(patch);
    }
}

/*

        + top
       /|\
      / | \
 left+--+--+ right
      \ | /
       \|/
        + bottom
*/
static ceVector3f calc_normal (ceVector3f* p, ceVector3f* left, ceVector3f* right, ceVector3f* bottom, ceVector3f* top)
{
  ceVector3f norm (0, 0, 0);
  if (top && right)
    {
      norm += ((*top - *p) % (*right - *p)).Normalized();
    }
  if (right && bottom)
    {
      norm += ((*right - *p) % (*bottom - *p)).Normalized();
    }
  if (bottom && left)
    {
      norm += ((*bottom - *p) % (*left - *p)).Normalized();
    }
  if (left && top)
    {
      norm += ((*left - *p) % (*top - *p)).Normalized();
    }
  norm.Normalize();
  return norm;
}

ceVector3f* get_pos (unsigned char* vertices, unsigned offset, unsigned stride,  int p, int q, int np, int nq, int dp, int dq)
{
  p += dp;
  q += dq;
  if (   p < 0
      || p >= np
      || q < 0
      || q >= nq)
    {
      return 0;
    }

  return reinterpret_cast<ceVector3f*>(&vertices[offset + (q * np + p) * stride]);
}


bool cePatchMeshGL20::CalcSmoothNormals()
{
  assert (_vertices);
  assert (_vertexDeclaration);

  static ceProgramParameterName positionName (PPB_Position1);
  static ceProgramParameterName normalName(PPB_Position1);

  const ceVertexElement* posElem = _vertexDeclaration->GetElement(positionName, 0);
  const ceVertexElement* normElem = _vertexDeclaration->GetElement(normalName, 0);
  if (!posElem || !normElem)
    {
      return false;
    }
  unsigned char* vertices;
  if (!_vertices->Lock(0, (void**)(&vertices), BAM_ReadWrite))
    {
      return false;
    }
  for (unsigned q=0; q<_numQ; q++)
    {
      for (unsigned p=0; p<_numP; p++)
        {

          ceVector3f* pos    = get_pos (vertices, posElem->Offset, posElem->Stride, p, q, _numP, _numQ,  0,  0);
          ceVector3f* left   = get_pos (vertices, posElem->Offset, posElem->Stride, p, q, _numP, _numQ, -1,  0);
          ceVector3f* right  = get_pos (vertices, posElem->Offset, posElem->Stride, p, q, _numP, _numQ,  1,  0);
          ceVector3f* bottom = get_pos (vertices, posElem->Offset, posElem->Stride, p, q, _numP, _numQ,  0, -1);
          ceVector3f* top    = get_pos (vertices, posElem->Offset, posElem->Stride, p, q, _numP, _numQ,  0,  1);


          ceVector3f* norm   = get_pos (vertices, normElem->Offset, normElem->Stride, p, q, _numP, _numQ,  0,  0);
          *norm = calc_normal (pos, left, right, bottom, top);
        }
    }
  _vertices->Unlock();
  return true;
}

bool cePatchMeshGL20::UpdatePatchBoundingBoxes(float distance)
{

	ceBoundingBoxBuilder bbb;
	if (!bbb.Initialize (_vertices, _vertexDeclaration))
		{
			return false;
		}

  _bbox.Clear();
  for (unsigned i=0, j=_patches.size(); i<j; i++)
    {
      Patch* patch = _patches[i];
			patch->bbox = bbb.CreateBoundingBox (patch->offset, _numP, patch->dp, patch->dq);
			patch->maxDistance = patch->bbox.GetOuterRadius () + distance;
      _bbox.Add(patch->bbox);
    }
  _bbox.Update();
	_maxDistance = distance;

	return true;
}

inline unsigned cePatchMeshGL20::GetNeighbourStepP (Patch* patch, cePatchMeshGL20::PatchNeighbour neighbour)
{
  Patch* pn = patch->neighbours[neighbour];
  if (pn)
    {
      return pn->stepP < patch->stepP ? pn->stepP : patch->stepP;
    }
  return patch->stepP;
}

inline unsigned cePatchMeshGL20::GetNeighbourStepQ (Patch* patch, cePatchMeshGL20::PatchNeighbour neighbour)
{
  Patch* pn = patch->neighbours[neighbour];
  if (pn)
    {
      return pn->stepQ < patch->stepQ ? pn->stepQ : patch->stepQ;
    }
  return patch->stepQ;
}

#define PATCH_FAN_POS(ibase, i0, i1, step) for (k=i0; k<i1; k+=step) { *iptr++ = ibase; *iptr++ = k; *iptr++ = (k+step); num+=3;}
#define PATCH_FAN_NEG(ibase, i0, i1, step) for (k=i0; k<i1; k+=step) { *iptr++ = ibase; *iptr++ = (k+step); *iptr++ = k; num+=3;}

void cePatchMeshGL20::UpdatePatch(Patch *patch)
{
  if (!patch)
    {
      return;
    }
  unsigned num = (patch->dp-1) * (patch->dq-1) * 6;
  unsigned *indices = new unsigned[num];
  unsigned* iptr = indices;

  if (patch->stepP == 1 || patch->stepQ == 1)
    {
      unsigned offset = patch->offset;
      for (unsigned q=0; q<(patch->dq-1); q++)
        {
          unsigned idx0 = offset +  q    * _numP;
          unsigned idx1 = offset + (q+1) * _numP;
          for (unsigned p=0; p<(patch->dp-1); p++)
            {
              unsigned idx00 = idx0 + p;
              unsigned idx01 = idx0 + p + 1;
              unsigned idx10 = idx1 + p;
              unsigned idx11 = idx1 + p + 1;

              *iptr++ = idx00;
              *iptr++ = idx01;
              *iptr++ = idx11;
              *iptr++ = idx00;
              *iptr++ = idx11;
              *iptr++ = idx10;
            }
        }
    }
  else
    {
      unsigned numP = (patch->dp - 1) / patch->stepP;
      unsigned numQ = (patch->dq - 1) / patch->stepQ;
      num = 0;
      unsigned k=0;
      for (unsigned q=0; q<numQ; q++)
        {
          unsigned stepNP = q == 0        ? GetNeighbourStepP(patch, PN_NegQ) : patch->stepP;
          unsigned stepPP = q == (numQ-1) ? GetNeighbourStepP(patch, PN_PosQ) : patch->stepP;

          unsigned baseQ = q * patch->stepQ + (patch->stepQ >> 1);
          unsigned q0 = q * patch->stepQ;
          unsigned q1 = q0 + patch->stepQ;
          for (unsigned p=0; p<numP; p++)
            {
              unsigned stepNQ = p == 0        ? GetNeighbourStepQ(patch, PN_NegP) : patch->stepQ;
              unsigned stepPQ = p == (numP-1) ? GetNeighbourStepQ(patch, PN_PosP) : patch->stepQ;

              unsigned baseP = p * patch->stepP + (patch->stepP >> 1);
              unsigned p0 = p * patch->stepP;
              unsigned p1 = p0 + patch->stepP;

              unsigned base = patch->offset + baseQ * _numP + baseP;
              unsigned i00 = patch->offset + q0 * _numP + p0;
              unsigned i01 = patch->offset + q0 * _numP + p1;
              unsigned i10 = patch->offset + q1 * _numP + p0;
              unsigned i11 = patch->offset + q1 * _numP + p1;

              unsigned st0 = num;

              PATCH_FAN_POS(base, i00, i01, stepNP);
              PATCH_FAN_POS(base, i01, i11, (stepPQ * _numP));
              PATCH_FAN_NEG(base, i10, i11, stepPP);
              PATCH_FAN_NEG(base, i00, i10, (stepNQ * _numP));

            }
        }
    }

  patch->indexBuffer->Copy(0, num * sizeof(unsigned), indices);
  patch->assignedIndices = num;
  patch->dirty = false;
  delete [] indices;
}

void cePatchMeshGL20::Copy(iDevice* device, iPatchMesh *pmesh)
{
  cePatchMeshGL20* mesh = static_cast<cePatchMeshGL20*>(pmesh);

  mesh->_bbox = _bbox;
  mesh->_numP = _numP;
  mesh->_numQ = _numQ;
  mesh->_numPatchP = _numPatchP;
  mesh->_numPatchQ = _numPatchQ;
  mesh->_vertices = _vertices;
  mesh->_vertexDeclaration = _vertexDeclaration;
  mesh->_bbox = _bbox;
  mesh->CreatePatches(device, _numPatchP, _numPatchQ, _wrapP, _wrapQ);
  mesh->UpdatePatchBoundingBoxes(_maxDistance);
}

iPatchMesh* cePatchMeshGL20::Clone(iDevice* device)
{
  cePatchMeshGL20* mesh = new cePatchMeshGL20 ();
  Copy (device, mesh);
  return mesh;
}

